Skip to content

stack 3/7: carry six contributor bug fixes with authorship intact - #953

Merged
lidge-jun merged 9 commits into
devfrom
codex/carry-contributor-bugfixes
Aug 4, 2026
Merged

stack 3/7: carry six contributor bug fixes with authorship intact#953
lidge-jun merged 9 commits into
devfrom
codex/carry-contributor-bugfixes

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Stack

3/3 — carried contributor bug fixes

Base: codex/908-long-context-pricing (#952)

Summary

Carries six contributor bug fixes onto the stack, unmodified, with original authorship preserved via git cherry-pick -x. Every commit keeps its author; git log --format='%an' shows the contributor, not me.

Source PR Author Carried commits Fix
#939 @DevMello 1 keep a marker for tool_result document blocks
#943 @DevMello 1 fail closed on MALFORMED_FUNCTION_CALL without a call part
#944 @DevMello 1 separate reasoning summary parts in streamed thinking
#945 @DevMello 1 report upstream usage for native compact turns
#942 @L14nY1Wang 1 close passthrough streams at terminal events
#948 @mushikingh 3 round-trip the Kiro redactedContent reasoning blob

Eight commits, +604/-70 across 20 files. No content changes were made to any of them — this is a carry, not a rework. Each applied to dev cleanly with no conflict resolution, so what is reviewed here is byte-identical to what the authors wrote.

Why carry rather than merge in place

These are small, focused, independently-correct fixes that were sitting unmerged behind a long review queue. Stacking them puts them on one branch with one CI run and one review pass, in the same bottom-up order as the rest of the stack.

The source PRs stay open until this lands. If a maintainer prefers to take any of them directly instead, that path is unaffected — drop the corresponding commits here and merge the original. Once this merges, each source PR can be closed as carried, with credit already recorded in the commit history rather than in a comment.

Not carried, and why

PR Author Reason
#947 @WZBbiao conflicts with #942's terminal-event handling — both touch the same relay path, and resolving that is the author's call, not a carry
#933 @IMHinnG draft, enforce-target failing
#928, #922, #940, #935 CHANGES_REQUESTED — carrying a PR past requested changes would route around the review

Verification

  • bun x tsc --noEmit — exit 0
  • bun test on all 11 touched test files — 331 pass, 0 fail
  • bun run test — 7691 pass, 8 skip, 0 fail, 507 files
  • bun run privacy:scan — passed

Full-suite green is what matters here: these six fixes touch Claude inbound/outbound, Google streaming, compaction, passthrough terminals, and the Kiro adapter, and nothing in the suite regressed.

Summary by CodeRabbit

  • New Features

    • Added support for preserving Kiro redacted reasoning and context-usage updates across streaming and batch responses.
    • Improved document handling in tool results with readable text placeholders.
    • Added usage details for successful compact responses.
  • Bug Fixes

    • Improved Vertex AI handling of truncated tool-call responses.
    • Fixed reasoning formatting across streamed Claude responses.
    • Improved SSE relay termination, [DONE] handling, and cleanup of trailing data.
  • Documentation

    • Clarified platform-specific passthrough relay behavior and Kiro reasoning support.

DevMello and others added 8 commits August 4, 2026 00:42
A tool_result whose content is a document block translated to an
empty tool output, so routed models treated the attachment as a tool
that returned nothing. Surface the same "[document: title]" marker
the user-message path already emits.

(cherry picked from commit 3cdf190)
Gemini usually drops the malformed call upstream, so the final chunk
carries only the finishReason and the started-calls guard never fired.
The turn surfaced as a clean empty completion instead of an error.
MAX_TOKENS with no started call keeps its plain token-limit stop.

(cherry picked from commit 6eb5903)
The streaming translator folded every summary part into one thinking
block with no separator, so multi-part summaries rendered as run-on
text. The JSON path already joins parts with a blank line; the stream
now emits the same separator at part boundaries.

(cherry picked from commit b23cc1f)
The native branch buffers the upstream compact JSON and returns it
without inspecting the body, so the request log row lands with no
usage. Lift usage and response metadata from the buffered body the
same way the routed branch gets it through handleResponses.

(cherry picked from commit 2ae3b5f)
Kiro never returns plaintext reasoning for its Sol-family models. Its
`reasoningContentEvent` carries a KMS-encrypted `redactedContent` blob, and
`gpt-5.6-sol`'s `additionalModelRequestFieldsSchema` accepts only
`reasoning.effort` — there is no display or summary opt-in. Kiro's own CLI
replays that blob on the matching `assistantResponseMessage.reasoningContent`
to preserve model reasoning across turns.

The adapter read only `reasoningContentEvent.text`, which is absent on this
wire, so the blob was dropped and never replayed. Every turn therefore
restarted without the previous turn's reasoning.

- kiro-events: parse `redactedContent`; add the previously unhandled
  `contextUsageEvent` (Kiro reports context pressure there, not in
  `metadataEvent`, which carries only `stopReason`).
- Carry the blob through the existing `ocxr1:` envelope as `krc` on an
  envelope-only reasoning item, so it round-trips while staying invisible in
  the app — the same contract the hidden-thinking path already uses.
- Pair it backwards: Kiro emits the event at the END of a turn, after content
  and tool calls, so a krc-only item belongs to the assistant turn that already
  closed. Folding it forward would attach turn N's blob to turn N+1. With no
  assistant turn to own it, the blob is dropped rather than mis-paired.
- Replay it on `assistantResponseMessage.reasoningContent`.

Verified against kiro-cli 2.14.1 and 2.16.0 request/response captures.

(cherry picked from commit cbb5d21)
CodeRabbit caught a real defect that made the round-trip a no-op in the
streaming path.

Kiro sends its reasoning blob at the END of a turn, while the assistant message
is still open. Emitting the envelope-only item on arrival was wrong twice over:

- `outputIndex` only advances when an item CLOSES, so the open message and the
  envelope item were emitted under the same output index.
- The envelope landed BEFORE the assistant message, and the parser pairs a
  krc-only item backwards, so it found no preceding assistant turn and dropped
  the blob as orphaned — silently defeating the fix.

Both paths now stash the blob and flush it after every open item has closed:
after the closes in the streaming `done` case, and after the trailing flushes in
the batch path. Message phase inference is untouched, so a Kiro final answer is
still classified `final_answer` rather than being force-closed as commentary.

The batch path also released `bytesOf(encrypted)` through `pushOutput` without
ever retaining it. It now charges the blob when stashing and lets `pushOutput`
release that retained allocation, so the translator budget balances.

Adds tests/kiro-reasoning-roundtrip.test.ts, which bridges adapter events and
re-parses the emitted items the way Codex replays history — the end-to-end
coverage the original tests lacked. Three of its five cases fail against the
previous commit.

Scope: verified that gpt-5.6-terra and gpt-5.6-luna return `redactedContent`
exactly like gpt-5.6-sol, so the whole GPT-5.6 family was affected. Handling
keys off the wire field, not the model id.

(cherry picked from commit bd13d48)
…blob

The batch budget assertion was vacuous twice over. It passed the budget in the
wrong argument position, so `buildResponseJSON` built its own internal budget and
the snapshot under test was never written to (`highWaterBytes: 0`). Even wired
correctly, asserting `currentBytes >= 0` could not distinguish a leaked raw blob
from the finalized items that legitimately stay retained.

It now uses a 4 KB blob and asserts `currentBytes` equals EXACTLY the finalized
output items' bytes — a still-retained raw blob shows up as ~4 KB of excess, and
releasing bytes that were never charged shows up as a shortfall. A separate
`highWaterBytes` assertion proves the blob was charged while held rather than
never accounted for. Four of the file's five cases now fail against cbb5d21.

Also corrects the metadataEvent description: every capture put the context
percentage in `contextUsageEvent`, but the parser still accepts a finite
`contextUsagePercentage` (and `tokenUsage`) from `metadataEvent` as a fallback,
so documenting it as impossible was wrong.

(cherry picked from commit 8610630)
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Terminal-aware SSE transport

Layer / File(s) Summary
SSE terminal boundary
src/server/relay.ts, tests/sse-failed-tail.test.ts
Adds protocol-aware SSE buffering. Terminal events close the client stream, preserve complete frames, append [DONE] when needed, and cancel upstream reads.
Eager relay integration
src/server/relay-eager.ts, tests/relay-eager.test.ts
Processes chunks through the boundary before rewriting. It flushes tails, suppresses post-terminal data, and disposes state during teardown.
Relay routing and policy
src/server/responses/core.ts, src/server/index.ts, src/lib/bun-stream-caps.ts, structure/04-transports-and-sidecars.md, tests/passthrough-abort.test.ts
Uses inspector terminal state for eager closure and updates Windows and Darwin relay selection rules.

Kiro reasoning round-trip

Layer / File(s) Summary
Kiro event and envelope contracts
src/adapters/kiro-events.ts, src/responses/reasoning-envelope.ts, src/types.ts
Adds redacted-reasoning and context-usage event data, plus krc envelope and assistant-message metadata.
Kiro adapter propagation
src/adapters/kiro.ts
Preserves redacted reasoning in assistant history, serializes it in requests, emits replay events, and records context usage.
Streaming and batch bridge buffering
src/bridge.ts
Buffers the latest reasoning blob, retains its translator budget, and emits it after related assistant output closes.
Kiro replay and usage validation
src/responses/parser.ts, tests/anthropic-thinking-signature.test.ts, tests/kiro-adapter.test.ts, tests/kiro-reasoning-roundtrip.test.ts, tests/kiro-stream.test.ts, structure/04-transports-and-sidecars.md
Attaches krc reasoning to the preceding assistant message and validates SSE, batch, replay, ordering, and budget behavior.

Provider and adapter behavior

Layer / File(s) Summary
Vertex truncation classification
src/adapters/google-truncation.ts, src/adapters/google.ts, tests/google-vertex-stream.test.ts
Fails closed for malformed function calls and tool-call truncation in both streaming and non-streaming parsing.
Claude content and reasoning formatting
src/claude/inbound.ts, src/claude/outbound.ts, tests/claude-inbound.test.ts, tests/claude-outbound.test.ts
Preserves tool-result documents as text markers and separates distinct reasoning parts with blank lines.

Response usage inspection

Layer / File(s) Summary
Native compact usage reporting
src/server/responses/compact.ts, tests/responses-compaction-routing.test.ts
Inspects successful buffered compact responses and records usage in response metadata and request logs.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Suggested labels: bug

Suggested reviewers: wibias, ingwannu

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the PR as the third stack layer carrying six contributor bug fixes while preserving authorship.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/carry-contributor-bugfixes

Comment @coderabbitai help to get the list of available commands.

@lidge-jun

Copy link
Copy Markdown
Owner Author

Stack navigation

  1. stack 1/6: triage the open issue surface and lock the bug plan #951 — triage the open issue surface and lock the bug plan (base dev)
  2. stack 2/7: price long-context requests at the published long rate (#908) #952 — long-context pricing tiers, Cost estimates ignore published long-context pricing tiers (OpenAI >272k, xAI >=200k) #908 (base stack 1/6: triage the open issue surface and lock the bug plan #951)
  3. stack 3/7: carry six contributor bug fixes with authorship intact #953 — carry six contributor bug fixes (base stack 2/7: price long-context requests at the published long rate (#908) #952)

Review and merge bottom-up. Each PR targets the preceding stack branch, so its Files changed view contains only that layer.

The layers touch disjoint files — devlog/, src/usage/, and the carried contributors' paths — so any layer can be retargeted to dev and taken independently without a rebase conflict if you prefer.

Carried in #953, with authorship preserved: #939, #942, #943, #944, #945, #948.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9664abdd18

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const clientBody = process.platform === "win32" && !needsClientRewrite
? nativeBody
: relaySseWithFailedTail(rewrittenBody, upstream, reason => clientGone.abort(reason));
const clientBody = relaySseWithFailedTail(rewrittenBody, upstream, reason => clientGone.abort(reason));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the native Windows tee client path

On Windows with the default streamMode: "auto" or explicit legacy-tee, no-rewrite traffic does not enter the eager branch because MIN_FIXED_BUN_VERSION is still null; it therefore reaches this unconditional relaySseWithFailedTail JS pull wrapper. This recreates the Bun#32111 path that the removed nativeBody branch avoided, so the bundled known-bad runtime can crash or lose terminal frames. Keep the native client branch for Windows decisions where useEagerRelay is false, or only use a JS relay once the runtime is proven fixed.

AGENTS.md reference: AGENTS.md:L206-L208

Useful? React with 👍 / 👎.

Comment thread src/server/relay.ts
return {
feed(chunk) {
if (disposed || terminal) return new Uint8Array(0);
buffer += decoder!.decode(chunk, { stream: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bound incomplete frames in the terminal relay

When an upstream sends a very large or never-terminated SSE event, every chunk is appended to buffer until a blank-line delimiter arrives, with no byte cap or timeout. This new boundary is now on passthrough response paths, so a stuck or hostile compatible gateway can grow proxy memory without limit and withhold all of that event from the client. Apply a bounded frame policy and fail closed or safely fall back when the cap is exceeded.

AGENTS.md reference: src/AGENTS.md:L17-L17

Useful? React with 👍 / 👎.

@lidge-jun

Copy link
Copy Markdown
Owner Author

Stack navigation

  1. stack 1/6: triage the open issue surface and lock the bug plan #951 — triage the open issue surface and lock the bug plan (base dev)
  2. stack 2/7: price long-context requests at the published long rate (#908) #952 — long-context pricing tiers, Cost estimates ignore published long-context pricing tiers (OpenAI >272k, xAI >=200k) #908 (base stack 1/6: triage the open issue surface and lock the bug plan #951)
  3. stack 3/7: carry six contributor bug fixes with authorship intact #953 — carry six contributor bug fixes (base stack 2/7: price long-context requests at the published long rate (#908) #952)
  4. stack 4/7: keep an explicit thinking disable through translation (#545) #954 — Claude Desktop classifier thinking round-trip, Claude Desktop 3P Auto Mode classifier retries after 64-token Anthropic OAuth outputs #545 (base stack 3/7: carry six contributor bug fixes with authorship intact #953)

Review and merge bottom-up. Each PR targets the preceding stack branch, so its Files changed view contains only that layer.

The layers touch disjoint files — devlog/, src/usage/, the carried contributors' paths, and src/claude/ + src/adapters/anthropic.ts — so any layer can be retargeted to dev and taken independently without a rebase conflict.

#954 needs human security review per MAINTAINERS.md: it changes request construction on an Anthropic OAuth execution path. It is last in the stack so the first three can land without waiting on that.

Carried in #953, with authorship preserved: #939, #942, #943, #944, #945, #948.

@lidge-jun lidge-jun changed the title stack 3/3: carry six contributor bug fixes with authorship intact stack 3/4: carry six contributor bug fixes with authorship intact Aug 3, 2026
@lidge-jun

Copy link
Copy Markdown
Owner Author

Stack navigation

  1. stack 1/6: triage the open issue surface and lock the bug plan #951 — triage the open issue surface and lock the bug plan (base dev)
  2. stack 2/7: price long-context requests at the published long rate (#908) #952 — long-context pricing tiers, Cost estimates ignore published long-context pricing tiers (OpenAI >272k, xAI >=200k) #908 (base stack 1/6: triage the open issue surface and lock the bug plan #951)
  3. stack 3/7: carry six contributor bug fixes with authorship intact #953 — carry six contributor bug fixes (base stack 2/7: price long-context requests at the published long rate (#908) #952)
  4. stack 4/7: keep an explicit thinking disable through translation (#545) #954 — Claude Desktop classifier thinking round-trip, Claude Desktop 3P Auto Mode classifier retries after 64-token Anthropic OAuth outputs #545 (base stack 3/7: carry six contributor bug fixes with authorship intact #953)
  5. stack 5/7: probe reset-derived cooldowns without waiting to be selected (#915) #955 — cooldown early-recovery probe, [Bug]: Reset-derived cooldowns can miss early recovery while another pool account remains eligible #915 (base stack 4/7: keep an explicit thinking disable through translation (#545) #954)

Review and merge bottom-up. Each PR targets the preceding stack branch, so its Files changed view contains only that layer.

The layers touch disjoint files — devlog/, src/usage/, the carried contributors' paths, src/claude/ + src/adapters/anthropic.ts, and src/codex/ — so any layer can be retargeted to dev and taken independently without a rebase conflict.

#954 needs human security review per MAINTAINERS.md: it changes request construction on an Anthropic OAuth execution path. #955 sits above it in the chain but is independent of it in code, so if that review blocks, #955 can be retargeted to #953 without conflict.

Carried in #953, with authorship preserved: #939, #942, #943, #944, #945, #948.

@lidge-jun lidge-jun changed the title stack 3/4: carry six contributor bug fixes with authorship intact stack 3/5: carry six contributor bug fixes with authorship intact Aug 3, 2026
@lidge-jun

Copy link
Copy Markdown
Owner Author

Source PRs are now closed as carried

All six have been closed and point here. Each was verified byte-identical before closing — git patch-id --stable on the original commit versus the carried one, not a title match:

Source Author Carried commit(s) patch-id
#939 @DevMello c233a7cf6 identical
#943 @DevMello 6dbd9723c identical
#944 @DevMello 528cfc12d identical
#945 @DevMello 0bb282062 identical
#942 @L14nY1Wang 02ca79a37 identical
#948 @mushikingh a4c78b270, 6cda4a160, 9664abdd1 identical

Authorship is preserved by cherry-pick -x, so git log --format='%an' on this branch shows the contributors rather than me.

This changes the failure mode, so it is worth stating plainly. My earlier comments said the sources would stay open until this merged. They are now closed while this PR is still under review, which means if this stack does not land, those six fixes are no longer sitting in the queue on their own. Reopening any of them is one click and their branches are untouched — but the responsibility for that now sits with this PR rather than with them.

@lidge-jun

Copy link
Copy Markdown
Owner Author

Stack navigation

  1. stack 1/6: triage the open issue surface and lock the bug plan #951 — triage the open issue surface and lock the bug plan (base dev)
  2. stack 2/7: price long-context requests at the published long rate (#908) #952 — long-context pricing tiers, Cost estimates ignore published long-context pricing tiers (OpenAI >272k, xAI >=200k) #908 (base stack 1/6: triage the open issue surface and lock the bug plan #951)
  3. stack 3/7: carry six contributor bug fixes with authorship intact #953 — carry six contributor bug fixes (base stack 2/7: price long-context requests at the published long rate (#908) #952)
  4. stack 4/7: keep an explicit thinking disable through translation (#545) #954 — Claude Desktop classifier thinking round-trip, Claude Desktop 3P Auto Mode classifier retries after 64-token Anthropic OAuth outputs #545 (base stack 3/7: carry six contributor bug fixes with authorship intact #953)
  5. stack 5/7: probe reset-derived cooldowns without waiting to be selected (#915) #955 — cooldown early-recovery probe, [Bug]: Reset-derived cooldowns can miss early recovery while another pool account remains eligible #915 (base stack 4/7: keep an explicit thinking disable through translation (#545) #954)
  6. stack 6/7: triage the overnight PRs and fix the #955 defects they found #973 — overnight PR triage + the stack 5/7: probe reset-derived cooldowns without waiting to be selected (#915) #955 defects it surfaced (base stack 5/7: probe reset-derived cooldowns without waiting to be selected (#915) #955)

Review and merge bottom-up. Each PR targets the preceding stack branch, so its Files changed view contains only that layer.

The layers touch largely disjoint files, so any layer can be retargeted to dev and taken independently. The one real dependency is #973 on #955 — it fixes defects in #955's own code, so those two should land together or in that order.

#954 needs human security review per MAINTAINERS.md (Anthropic OAuth request construction). It is deliberately below #955/#973 so the first three can land without waiting on it.

Carried with authorship preserved: #939, #942, #943, #944, #945, #948 in #953; #965, #967, #968 in #973.

@lidge-jun lidge-jun changed the title stack 3/5: carry six contributor bug fixes with authorship intact stack 3/6: carry six contributor bug fixes with authorship intact Aug 4, 2026
@lidge-jun lidge-jun changed the title stack 3/6: carry six contributor bug fixes with authorship intact stack 3/7: carry six contributor bug fixes with authorship intact Aug 4, 2026
@lidge-jun

Copy link
Copy Markdown
Owner Author

Stack navigation — 7 layers, review and merge bottom-up

Layer PR Contents
1/7 #951 merged af3ddedb4 — 22 label corrections + the plan unit
2/7 #952 long-context pricing tiers (#908)
3/7 #953 six carried contributor bug fixes, authorship intact
4/7 #954 explicit thinking disable through translation (#545)
5/7 #955 cooldown early-recovery probe (#915)
6/7 #973 overnight PR triage + fixes to #955's own defects
7/7 #980 NIM vision classification (#956), service repair (#970), qwen3.8-max rename

Each layer targets the branch below it, so its diff only makes sense on that base — enforce-target skips the wrong-base gate for stacked children by design (AGENTS.md, Branch policy). Review bottom-up; a layer cannot merge before its parent lands.

Note for the merge sequence: retargeting a child after its parent merges emits an edited event, which ci.yml does not listen for. A green check on the same head sha therefore proves nothing about the new merge base — merge current dev into the child to force a synchronize run before merging it.

@lidge-jun
lidge-jun changed the base branch from codex/908-long-context-pricing to dev August 4, 2026 03:47
# Conflicts:
#	tests/responses-compaction-routing.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/bridge.ts`:
- Around line 460-482: Update every terminal streaming path in the bridge,
including incomplete, error, adapter-EOF, and stall-timeout handling, to call
flushKiroRedactedReasoning() after closing open output items and before emitting
the terminal Responses event. Preserve the existing done behavior, ensure
pending data is released and emitted on abnormal termination, and add regression
coverage for incomplete and error after receiving a Kiro blob.

In `@src/server/index.ts`:
- Around line 330-332: Update the relay-policy comment near the gated-shape
logic to distinguish Windows rewrite traffic, which always uses eager relay,
from Windows no-rewrite traffic, which follows selectEagerPath and may remain
tee for legacy-tee or known-bad-runtime auto decisions.

In `@src/server/relay.ts`:
- Around line 111-153: Bound the incomplete-frame accumulation in
createSseTerminalOutputBoundary, including the decoder state used by feed, so
delimiter-free input cannot grow buffer indefinitely. When the byte cap is
exceeded, terminate processing with a bounded response.failed sequence and
release decoder state; ensure both relaySseWithFailedTail and
relaySseEagerBounded use this behavior. Add coverage for an open stream that
continually emits data without an SSE delimiter.

In `@src/server/responses/core.ts`:
- Around line 2035-2041: Update the response stream composition around
relaySseWithPayloadRewrite and relaySseWithFailedTail so nativeBody is first
passed through relaySseWithFailedTail, then the terminal-bounded stream is
passed to relaySseWithPayloadRewrite when payload rewrites exist. Preserve
nativeBody directly when no rewrites are configured, and add a regression test
covering a payload rewrite with a coalesced response.completed frame followed by
an oversized tail.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ca082d0b-ee05-46e5-a515-fa95177a8075

📥 Commits

Reviewing files that changed from the base of the PR and between d086a54 and fac1521.

📒 Files selected for processing (28)
  • src/adapters/google-truncation.ts
  • src/adapters/google.ts
  • src/adapters/kiro-events.ts
  • src/adapters/kiro.ts
  • src/bridge.ts
  • src/claude/inbound.ts
  • src/claude/outbound.ts
  • src/lib/bun-stream-caps.ts
  • src/responses/parser.ts
  • src/responses/reasoning-envelope.ts
  • src/server/index.ts
  • src/server/relay-eager.ts
  • src/server/relay.ts
  • src/server/responses/compact.ts
  • src/server/responses/core.ts
  • src/types.ts
  • structure/04_transports-and-sidecars.md
  • tests/anthropic-thinking-signature.test.ts
  • tests/claude-inbound.test.ts
  • tests/claude-outbound.test.ts
  • tests/google-vertex-stream.test.ts
  • tests/kiro-adapter.test.ts
  • tests/kiro-reasoning-roundtrip.test.ts
  • tests/kiro-stream.test.ts
  • tests/passthrough-abort.test.ts
  • tests/relay-eager.test.ts
  • tests/responses-compaction-routing.test.ts
  • tests/sse-failed-tail.test.ts

Comment thread src/bridge.ts
Comment on lines +460 to +482
// Kiro reasoning round-trip. Kiro sends its encrypted blob at the END of a turn, while the
// assistant message is still open, so this CANNOT emit on arrival: the open message still
// owns `outputIndex` (it only advances on close), and an item emitted here would both reuse
// that index and land BEFORE the message — where the parser's backwards pairing drops it as
// orphaned. Stash it and flush after `done` has closed every open item instead.
let pendingKiroRedacted: string | undefined;
let pendingKiroRedactedBytes = 0;
const flushKiroRedactedReasoning = () => {
if (!pendingKiroRedacted) return;
const previousBytes = pendingKiroRedactedBytes;
const encrypted = encodeReasoningEnvelope({ krc: pendingKiroRedacted });
const reservation = budget?.reserveTransient(bytesOf(encrypted), { kind: "reasoning" });
pendingKiroRedacted = undefined;
pendingKiroRedactedBytes = 0;
reservation?.commitRetained();
budget?.releaseRetained(previousBytes, { kind: "reasoning" });
const itemId = `rs_${uuid()}`;
const item = { type: "reasoning", id: itemId, summary: [] as never[], encrypted_content: encrypted };
emit("response.output_item.added", { output_index: outputIndex, item });
emit("response.output_item.done", { output_index: outputIndex, item });
retainFinishedItem(item as OutputItem, bytesOf(encrypted), "reasoning");
outputIndex++;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Flush the Kiro blob on every terminal path.

Lines 1100-1102 flush pendingKiroRedacted only for done. The incomplete, error, adapter-EOF, and stall-timeout paths do not flush or release it. A stream that receives kiro_redacted_reasoning and then terminates abnormally loses the replay state. It also leaves the retained blob charged in an external TranslatorBudget.

Call flushKiroRedactedReasoning() after closing open output items in every terminal path, before emitting the terminal Responses event. Add regression tests for incomplete and error after a Kiro blob. This aligns streaming behavior with batch finalization at Lines 1717-1725.

Also applies to: 921-926, 1100-1102

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bridge.ts` around lines 460 - 482, Update every terminal streaming path
in the bridge, including incomplete, error, adapter-EOF, and stall-timeout
handling, to call flushKiroRedactedReasoning() after closing open output items
and before emitting the terminal Responses event. Preserve the existing done
behavior, ensure pending data is released and emitted on abnormal termination,
and add regression coverage for incomplete and error after receiving a Kiro
blob.

Comment thread src/server/index.ts
Comment on lines +330 to +332
// #314 gated shape: win32 always uses the terminal-aware eager relay so a keep-alive
// upstream cannot hold Codex open after response.completed; darwin no-rewrite traffic
// requires explicit config-eager opt-in (`auto` always stays tee on darwin).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the Windows relay-policy comment.

Line 330 says Windows always uses the eager relay. In src/server/responses/core.ts, Windows no-rewrite traffic uses eager relay only when selectEagerPath permits it. legacy-tee and a known-bad-runtime auto decision remain on tee.

State that Windows rewrite traffic always uses eager relay, while Windows no-rewrite traffic follows selectEagerPath.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/index.ts` around lines 330 - 332, Update the relay-policy comment
near the gated-shape logic to distinguish Windows rewrite traffic, which always
uses eager relay, from Windows no-rewrite traffic, which follows selectEagerPath
and may remain tee for legacy-tee or known-bad-runtime auto decisions.

Comment thread src/server/relay.ts
Comment on lines +111 to +153
export function createSseTerminalOutputBoundary(): SseTerminalOutputBoundary {
let decoder: TextDecoder | null = new TextDecoder();
const encoder = new TextEncoder();
let buffer = "";
let terminal = false;
let done = false;
let disposed = false;

const process = (flush: boolean): Uint8Array => {
if (disposed || terminal) return new Uint8Array(0);
let output = "";
let responsesTerminal = false;
for (;;) {
const next = nextSseBlock(buffer);
if (!next) break;
buffer = next.rest;
const payload = sseDataPayload(next.block);
if (!responsesTerminal) output += next.block + next.delimiter;
if (payload === "[DONE]") {
done = true;
if (responsesTerminal) output += next.block + next.delimiter;
continue;
}
if (!responsesTerminal && payload && terminalStatusFromSsePayload(payload)) {
responsesTerminal = true;
}
}
if (responsesTerminal) {
terminal = true;
buffer = "";
}
if (flush && !terminal && buffer.length > 0) {
output += buffer;
buffer = "";
}
return encoder.encode(output);
};

return {
feed(chunk) {
if (disposed || terminal) return new Uint8Array(0);
buffer += decoder!.decode(chunk, { stream: true });
return process(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Bound the incomplete SSE frame buffer.

Line 152 appends every undecimited fragment to buffer. A gateway can keep a malformed SSE frame open indefinitely. This retains unbounded decoded text in both relaySseWithFailedTail and relaySseEagerBounded.

Add a byte cap for the incomplete frame. If the cap is exceeded, fail the relay with a bounded response.failed sequence and release the decoder state. Add tests for an open stream that continually sends data without an SSE delimiter.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/relay.ts` around lines 111 - 153, Bound the incomplete-frame
accumulation in createSseTerminalOutputBoundary, including the decoder state
used by feed, so delimiter-free input cannot grow buffer indefinitely. When the
byte cap is exceeded, terminate processing with a bounded response.failed
sequence and release decoder state; ensure both relaySseWithFailedTail and
relaySseEagerBounded use this behavior. Add coverage for an open stream that
continually emits data without an SSE delimiter.

Source: Path instructions

Comment on lines +2035 to +2041
// Windows was handled by the eager terminal-aware branch above. Remaining
// tee traffic can use the JS relay to close on a protocol terminal and to
// convert a mid-stream reset into a clean response.failed event.
const rewrittenBody = payloadRewrites.length > 0
? relaySseWithPayloadRewrite(nativeBody, composeSsePayloadRewrites(...payloadRewrites), translatorBudget)
: nativeBody;
const clientBody = process.platform === "win32" && !needsClientRewrite
? nativeBody
: relaySseWithFailedTail(rewrittenBody, upstream, reason => clientGone.abort(reason));
const clientBody = relaySseWithFailedTail(rewrittenBody, upstream, reason => clientGone.abort(reason));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Apply terminal filtering before payload rewriting.

relaySseWithPayloadRewrite receives and budgets the full upstream chunk before relaySseWithFailedTail sees response.completed. If one chunk contains a terminal frame plus a large trailing frame, the rewrite buffer can reject the trailing data before the client receives the valid terminal frame.

Wrap nativeBody with relaySseWithFailedTail first. Then apply relaySseWithPayloadRewrite to that terminal-bounded stream. Add a regression test with a payload rewrite, a coalesced terminal frame, and an oversized post-terminal tail.

Proposed ordering
- const rewrittenBody = payloadRewrites.length > 0
-   ? relaySseWithPayloadRewrite(nativeBody, composeSsePayloadRewrites(...payloadRewrites), translatorBudget)
-   : nativeBody;
- const clientBody = relaySseWithFailedTail(rewrittenBody, upstream, reason => clientGone.abort(reason));
+ const terminalBody = relaySseWithFailedTail(nativeBody, upstream, reason => clientGone.abort(reason));
+ const clientBody = payloadRewrites.length > 0
+   ? relaySseWithPayloadRewrite(terminalBody, composeSsePayloadRewrites(...payloadRewrites), translatorBudget)
+   : terminalBody;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Windows was handled by the eager terminal-aware branch above. Remaining
// tee traffic can use the JS relay to close on a protocol terminal and to
// convert a mid-stream reset into a clean response.failed event.
const rewrittenBody = payloadRewrites.length > 0
? relaySseWithPayloadRewrite(nativeBody, composeSsePayloadRewrites(...payloadRewrites), translatorBudget)
: nativeBody;
const clientBody = process.platform === "win32" && !needsClientRewrite
? nativeBody
: relaySseWithFailedTail(rewrittenBody, upstream, reason => clientGone.abort(reason));
const clientBody = relaySseWithFailedTail(rewrittenBody, upstream, reason => clientGone.abort(reason));
// Windows was handled by the eager terminal-aware branch above. Remaining
// tee traffic can use the JS relay to close on a protocol terminal and to
// convert a mid-stream reset into a clean response.failed event.
const terminalBody = relaySseWithFailedTail(nativeBody, upstream, reason => clientGone.abort(reason));
const clientBody = payloadRewrites.length > 0
? relaySseWithPayloadRewrite(terminalBody, composeSsePayloadRewrites(...payloadRewrites), translatorBudget)
: terminalBody;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/responses/core.ts` around lines 2035 - 2041, Update the response
stream composition around relaySseWithPayloadRewrite and relaySseWithFailedTail
so nativeBody is first passed through relaySseWithFailedTail, then the
terminal-bounded stream is passed to relaySseWithPayloadRewrite when payload
rewrites exist. Preserve nativeBody directly when no rewrites are configured,
and add a regression test covering a payload rewrite with a coalesced
response.completed frame followed by an oversized tail.

Source: Path instructions

@lidge-jun
lidge-jun merged commit 53f390b into dev Aug 4, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants